﻿#region Helper functions

function Get-ScriptDirectory
{
	$Invocation = (Get-Variable MyInvocation -Scope 1).Value
	#Split-Path $Invocation.MyCommand.Path
	#
	# Does not work while debuging in PowerGui Script Editor, so ;-(
	#
	return "E:\WindowsPowerShell\google\trunk\vStorageDRS\"
}

function Get-CacheDir
{
	$path = Get-ScriptDirectory
	$path += "Cache\"
	return $path
}

function Get-StorageCat
{
	param `
	(
		[string]$name
	)
	
	$addSubCatMethod = `
	{
		$subCat = $args[0]
		$this.SubCatz[$subCat.Name] = $subCat
	}
	
	$newCat = New-Object System.Management.Automation.PSObject `
	| Add-Member -MemberType ScriptMethod -Name ToString -Value {("{0} object -> {1}" -f $this.PSObject.TypeNames[0], $this.Name)} -Force -PassThru `
	| Add-Member -MemberType NoteProperty -Name Name -Value $name -PassThru `
	| Add-Member -MemberType NoteProperty -Name SubCatz -Value @{} -PassThru `
	| Add-Member -MemberType ScriptMethod -Name AddSubCat -Value $addSubCatMethod -PassThru `
	| Add-Member -MemberType ScriptProperty -Name GetCatNames -Value { $this.SubCatz.Keys } -PassThru `
	| Add-Member -MemberType ScriptProperty -Name GetCatz -Value { $this.SubCatz.Values } -PassThru `
	| Add-Member -MemberType ScriptMethod -Name GetCatByName -Value { [ref]$this.SubCatz[$args[0]] } -PassThru `

	$newCat.PSObject.TypeNames.Clear()
	$newCat.PSObject.TypeNames.Insert(0,'SDRS StorageCat')
	return $newCat
}

function Get-VIStat
{
	param ($entity = $(Throw "This is a required parameter."),
		$start,
		$finish,
		[string]$stat,
		[string]$instance = "",
		[string]$interval,	
		[int]$maxsamples,
		$connection)
	
	$perfMgr = Get-View (Get-View ServiceInstance -Server $connection.Connection).content.perfManager -Server $connection.Connection
	
	# Create performance counter hashtable
	$pcTable = New-Object Hashtable 
	foreach($pC in $perfMgr.PerfCounter){
	if($pC.Level -ne 99){
		$pctable.Add(($pC.GroupInfo.Key + "." + $pC.NameInfo.Key + "." + $pC.RollupType),$pC.Key)
	}
	}
	
	# Test if entity is valid
	if(!(("HostSystem",
		"VirtualMachine",
		"ComputeResource",
		"ResourcePool") -contains $entity.gettype().Name)) {
	Throw "-Entity parameters should be of type HostSystem , VirtualMachine, ComputeResource or ResourcePool"
	}
	
	# Test if start is valid
	if($start -ne $null -and $start -ne ""){
	if($start.gettype().name -ne "DateTime") {
		Throw "-Start parameter should be a DateTime value"
	}
	}
	
	# Test if finish is valid
	if($finish -ne $null -and $finish -ne ""){
	if($finish.gettype().name -ne "DateTime") {
		Throw "-Start parameter should be a DateTime value"
	}
	}
	
	# Test start-finish interval
	if($start -ne "" -and $finish -ne "" -and $start -ge  $finish){
	Throw "-Start time should 'older' than -Finish time."
	}
	
	# Test if stat is valid
	$statarray = $stat.Split(" ")
	$unitarray =@()
	foreach($st in $statarray){
	if($pcTable[$st] -eq $null){
		Throw "-Stat parameter $st is invalid."
	}
	$pcInfo = $perfMgr.QueryPerfCounter($pcTable[$st])
	$unitarray += $pcInfo[0].UnitInfo.Key
	
	# Test if instance is valid
	$metricId = $perfMgr.QueryAvailablePerfMetric($entity.MoRef, $null, $null, $null)
	$found = $false
	$validInstances =@()
	foreach($metric in $metricId){
		if($metric.CounterId -eq $pcTable[$st]){
		if($metric.Instance -eq "") {$cInstance = '""'} else {$cInstance = $metric.Instance}
		$validInstances += $cInstance
		if($instance -eq $metric.Instance){$found = $true}
		}
	}
	if(!$found){
		#Write-Host "-Instance parameter invalid for requested stat: $st.`nValid values are: $validInstances"
	}
	}
	
	# Test for a valid $interval
	if($interval.ToString().Split(" ").count -gt 1){
	Throw "Only 1 interval allowed."
	}
	
	switch($interval){
	"RT" {$numinterval = 20}
	"HI1" {$numinterval = $perfMgr.HistoricalInterval[0].SamplingPeriod}
	"HI2" {$numinterval = $perfMgr.HistoricalInterval[1].SamplingPeriod}
	"HI3" {$numinterval = $perfMgr.HistoricalInterval[2].SamplingPeriod}
	"HI4" {$numinterval = $perfMgr.HistoricalInterval[3].SamplingPeriod}
	Default {
		Throw "-Interval parameter invalid. `nValid values are: $validInterval"
	}
	}
	
	# Test if $maxsamples is valid (only for real-time !)
	if($level -ne 5 -and $maxsamples -ne ""){
	Throw "-Maxsamples parameter is only valid for real time (-Interval 20) statistics"
	}
	
	$PQSpec = New-Object VMware.Vim.PerfQuerySpec
	$PQSpec.entity = $entity.MoRef
	$PQSpec.Format = "normal"
	$PQSpec.IntervalId =  $numinterval
	$PQSpec.MetricId = @()
	foreach($st in $statarray){
	$PMId = New-Object VMware.Vim.PerfMetricId
	$PMId.counterId = $pcTable[$st]
	if($instance -ne $null){
		$PMId.instance = $instance
	}
	$PQSpec.MetricId += $PMId
	}
	$PQSpec.StartTime = $start
	$PQSpec.EndTime = $finish
	
	$stats = $perfMgr.QueryPerf($PQSpec)
	
	# No data available (yet)
	
	if($stats[0].Value -eq $null) {return $null}
	
	# Extract data to custom object and return as array
	$data = @()
	for($i=0; $i -lt $stats[0].SampleInfo.Count; $i++){
	for($j=0; $j -lt $statarray.Count; $j++){
		$row = "" | Select CounterId, CounterName, Instance, Timestamp, Interval, Value, Unit
		$row.CounterId = $stats[0].Value[$j].Id.CounterId
		$row.CounterName = $statarray[$j]
		$row.Instance = $stats[0].Value[$j].Id.Instance
		$row.Timestamp = $stats[0].SampleInfo[$i].Timestamp
		$row.Interval = $stats[0].SampleInfo[$i].Interval
		$row.Value = $stats[0].Value[$j].Value[$i]
		$row.Unit = $unitarray[$j]
		$data += $row
	}
	}
	$data
}

function Get-StorageCategory
{
	param `
	(
		[string]$name,
		$storageCatz
	)
	
		foreach ( $cat in $storageCatz.GetCatz )
		{
			if ( $cat.GetCatz )
			{
				foreach ( $subCat in $cat.GetCatz )
				{
					$lookup = "^" + $subCat.Name
					if ( $name -match $lookup )
					{
						return $subCat.Name
					}
				}
			}
			else
			{
				$lookup = "^" + $cat.Name
				if ( $name -match $lookup )
				{
					return $cat.Name
				}
			}
		}
}

function Get-SdrsLunIndex
{
	param `
	(
		$luns,
		$name
	)
	
	$index = 0
	foreach ( $lun in $luns )
	{
		if ( $lun.Name -eq $name )
		{
			return $index
		}
		$index++
	}
	return $null
}
function Get-AppData {
	
	$path = ""
	if ( $env:APPDATA -ne $null )
	{
		$path = $env:APPDATA
	}
	elseif ( $env:USERPROFILE )
	{
		if ( Test-Path ( $env:USERPROFILE + "\Application Data" ) )
		{
			$path = $env:USERPROFILE + "\Application Data"
		}
	}
	Write-Output $path
}

function Export-PSCredential {
	param ( $Credential = (Get-Credential) )

	# Look at the object type of the $Credential parameter to determine how to handle it
	switch ( $Credential.GetType().Name ) {
		# It is a credential, so continue
		PSCredential		{ continue }
		# It is a string, so use that as the username and prompt for the password
		String				{ $Credential = Get-Credential -credential $Credential }
		# In all other caess, throw an error and exit
		default				{ Throw "You must specify a credential object to export to disk." }
	}
	
	# Create temporary object to be serialized to disk
	$export = "" | Select-Object Username, EncryptedPassword
	
	# Give object a type name which can be identified later
	$export.PSObject.TypeNames.Insert(0,’ExportedPSCredential’)
	
	$export.Username = $Credential.Username

	# Encrypt SecureString password using Data Protection API
	# Only the current user account can decrypt this cipher
	$export.EncryptedPassword = $Credential.Password | ConvertFrom-SecureString

	# Export using the Export-Clixml cmdlet
	$Path = (Get-AppData) + "\PSCredential"
	if ( -not (Test-Path $Path) )
	{
		New-Item -type directory -path $Path
	}
	$credPath = $Credential.Username -replace "^\\", ""
	$credPath = $credPath -replace "\\", "_BCKSLSH_"
	$Path += "\" + $credPath + ".enc.xml"
	$export | Export-Clixml $Path
	Write-Host -foregroundcolor Green "Credentials saved to: " -noNewLine

	# Return FileInfo object referring to saved credentials
	Get-Item $Path
}

function Import-PSCredential {
	param ( $Path = "" )

	$credPath = ""

	if ( $Path -eq "" )
	{
		#$Path = (Get-AppData) + "\PSCredential\" + $env:USERNAME + ".enc.xml"
		$credPath = $env:USERDOMAIN + "\" + $env:USERNAME
	}
	else
	{
		#$Path = (Get-AppData) + "\PSCredential\" + $Path + ".enc.xml"
		$credPath = $Path -replace "_BCKSLSH_", "\\"
	}

	$Path = (Get-AppData) + "\PSCredential\" + $credPath + ".enc.xml"
	
	# Import credential file
	$import = Import-Clixml $Path 
	
	# Test for valid import
	if ( !$import.UserName -or !$import.EncryptedPassword ) {
		Throw "Input is not a valid ExportedPSCredential object, exiting."
	}
	$Username = $import.Username
	
	# Decrypt the password and store as a SecureString object for safekeeping
	$SecurePass = $import.EncryptedPassword | ConvertTo-SecureString
	
	# Build the new credential object
	$Credential = New-Object System.Management.Automation.PSCredential $Username, $SecurePass
	Write-Output $Credential
}

#endregion

#region Get-Managed-Host function

function global:Get-ManagedHost
{
	param `
	(
		[string]$name,
		[string]$account
	)
	
	$getConnectedAccessor = `
	{
		[bool]$this.Connection
	}
	$connectMethod = `
	{
		if (-not $this.Connected)
		{
			Write-Host ( "Connecting to Vi server {0}" -f $this.Name )
			$this.Connection = Connect-VIServer -Server $this.Name -Credential (Import-PSCredential $this.ConnectionAccount)
			$this.Connection | Add-Member -MemberType NoteProperty -Name ServiceInstance -Value (Get-View -Id ServiceInstance -Server $this.Connection)
		}
	}
	$disconnectMethod = `
	{
		if ($this.Connected)
		{
			Disconnect-VIServer -Server $this.Connection -Confirm:$false -ErrorAction SilentlyContinue
		}
		$this.Connection = $null
		Remove-Variable -Name DefaultVIServer -ErrorAction SilentlyContinue
	}

	$newManagedHost = New-Object System.Management.Automation.PSObject `
	| Add-Member -MemberType ScriptMethod -Name ToString -Value {("{0} object -> {1}" -f $this.PSObject.TypeNames[0], $this.Name)} -Force -PassThru `
	| Add-Member -MemberType NoteProperty -Name Name -Value $name -PassThru `
	| Add-Member -MemberType NoteProperty -Name ConnectionAccount -Value $account -PassThru `
	| Add-Member -MemberType NoteProperty -Name Connection -Value $null -PassThru `
	| Add-Member -MemberType ScriptProperty -Name Connected -Value $getConnectedAccessor -PassThru `
	| Add-Member -MemberType ScriptMethod -Name Connect -Value $connectMethod -PassThru `
	| Add-Member -MemberType ScriptMethod -Name Disconnect -Value $disconnectMethod -PassThru `

	$newManagedHost.PSObject.TypeNames.Clear()
	$newManagedHost.PSObject.TypeNames.Insert(0,'ManagedVMwareHost')
	return $newManagedHost
}
#endregion

#region Get-StorageLayout

function Get-StorageLayout
{
	param `
	(
		[string]$name,
		[int]$maxHotVmsPerLun = 1,
		[int]$maxMediumVmsPerLun = 2,
		$storageCatz
	)
	
	$lunCountAccessor = `
	{
		$this.Luns.Count
	}
	$vmsAccessor = `
	{
		$array = @()
		$this.Luns | % { $array += $_.Vms }
		return $array
	}
	$vmCountAccessor = `
	{
		$i = 0
		foreach( $lun in $this.Luns )
		{
			$i += $lun.VmCount
		}
		return $i
	}
	$printLayoutMethod = `
	{
		foreach ( $catName in ( $this.StorageCatz.GetCatNames | Sort ) )
		{
			$this.PrintLayOutByCat($this.StorageCatz.GetCatByName($catName).Value)
		}
	}
	$printLayoutByCatMethod = `
	{
		$cat = $args[0]
		if ( $cat.GetCatz )
		{
			foreach( $subCatName in $cat.GetCatNames | Sort )
			{
				$this.PrintLayOutByCat($cat.GetCatByName($subCatName).Value)
			}
		}
		else
		{
			Write-Host ( "`n{0} - LUNs`n" -f $cat.Name)
			foreach ( $lun in ( $this.GetLunsByCat($cat) | Sort Name ) )
			{
				$lun.PrintLun()
			}
		}
	}
	$printLayoutByFreeGBMethod = `
	{
		$cat = ""
		if ( $args )
		{
			$cat = $args[0]
		}
		else
		{
			$cat = $this.StorageCatz
		}
		if ( $cat.GetCatz )
		{
			foreach( $subCat in $cat.GetCatz | Sort Name )
			{
				$this.PrintLayOutByFreeGB($subCat)
			}
		}
		else
		{
			Write-Host ( "`n{0} LUNs - Sorted by FreeGB" -f $cat.Name)
			$this.GetLunsByCat($cat) | Sort FreeGB | Select Name, FreeGB | ft -AutoSize
		}
	}
	$printLayoutByBurdenMethod = `
	{
		$cat = ""
		if ( $args )
		{
			$cat = $args[0]
		}
		else
		{
			$cat = $this.StorageCatz
		}
		if ( $cat.GetCatz )
		{
			foreach( $subCat in $cat.GetCatz | Sort Name )
			{
				$this.PrintLayOutByBurden($subCat)
			}
		}
		else
		{
			Write-Host ( "`n{0} LUNs - Sorted by Burden" -f $cat.Name)
			$this.GetLunsByCat($cat) | Sort Burden -Descending | Select Name, Burden | ft -AutoSize
		}
	}
	$printLayoutByNameMethod = `
	{
		$cat = ""
		if ( $args )
		{
			$cat = $args[0]
		}
		else
		{
			$cat = $this.StorageCatz
		}
		if ( $cat.GetCatz )
		{
			foreach( $subCat in $cat.GetCatz | Sort Name )
			{
				$this.PrintLayOutByName($subCat)
			}
		}
		else
		{
			#Write-Host ( "`n{0} LUNs - Sorted by FreeGB" -f $cat.Name)
			$this.GetLunsByCat($cat) | Sort Name | Select Name, CapacityGB, FreeGB, Burden | ft -AutoSize
		}
	}
	$addLunMethod = `
	{
		$lun = $args[0]
		
		$this.Luns += $lun
		$this.LunIndex[$lun.Name] = $this.Luns.Count - 1
	}
	$getLunByNameMethod = `
	{
		$lunName = $args[0]
		if ( -not ( $this.LunIndex.Keys -contains $lunName ) )
		{
			Throw ("Unable to find LunIndex of LUN $lunName")
		}
		$lunIndex = $this.LunIndex[$lunName]
		return [ref]$this.Luns[$lunIndex]
	}
	$addVmMethod = `
	{
		$vm = $null
		$lunName = $null
		if ( $args.Count -eq 1 )
		{
			$vm = $args[0]
			$lunName = $vm.CurrentLun
		}
		elseif ( $args.Count -eq 2 )
		{
			$vm = $args[0]
			$lunName = $args[1]
		}
		$lunRef = $this.GetLunByName($lunName)
		$lunRef.Value.AddVm($vm)
		$this.VmIndex[$vm.Name] = $this.LunIndex[$lunName]
	}
	$setVmIoTypesMethod = `
	{
		foreach ( $cat in $this.StorageCatz.GetCatz )
		{
			$this.SetVmIoTypesByCat($cat)
		}
	}
	$setVmIoTypesByCatMethod = `
	{
		$cat = $args[0]
		if ( $cat.GetCatz )
		{
			foreach ( $subCat in $cat.GetCatz )
			{
				$this.SetVmIoTypesByCat($subCat)
			}
		}
		else
		{
			$catName = $cat.Name
			$vms = $this.GetVmsByCat($cat) | Sort IOAverage -Descending
			$luns = $this.GetLunsByCat($cat)
			$lunCount = 0
			if ( $luns -is [Array] )
			{
				$lunCount = $luns.Count
			}
			elseif ( $luns -eq $null )
			{
				$lunCount = 0
			}
			else
			{
				$lunCount = 1
			}
			$hotCount = $this.MaxHotVmsPerLun * $lunCount
			$mediumCount = $hotCount + $this.MaxMediumVmsPerLun * $lunCount
			
			$i = 0
			foreach ( $vm in $vms )
			{
				if ( $i -lt $hotCount )
				{
					$this.GetVmByName($vm.Name).Value.IOType = "hot"
				}
				elseif ( $i -lt $mediumCount )
				{
					$this.GetVmByName($vm.Name).Value.IOType = "medium"
				}
				else
				{
					$this.GetVmByName($vm.Name).Value.IOType = "low"
				}
				$i++
			}

		}
	}
	$getLunsByCatMethod = `
	{
		$cat = $args[0]
		$this.Luns | where { $_.StorageCat -eq $cat.Name }
	}
	$getVmsByCatMethod = `
	{
		$cat = $args[0]
		$array = @()
		$this.Luns | where { $_.StorageCat -eq $cat.Name } | % { $array += $_.Vms }
		return $array
	}
	$getVmByNameMethod = `
	{
		$vmName = $args[0]
		if ( -not ( $this.VmIndex.Keys -contains $vmName ) )
		{
			Throw ("Unable to find lun index of Vm $vmName")
		}
		$lunIndex = $this.VmIndex[$vmName]
		return $this.Luns[$lunIndex].GetVmByName($vmName)
	}
	
	$newLayout = New-Object System.Management.Automation.PSObject `
	| Add-Member -MemberType ScriptMethod -Name ToString -Value {("{0} object -> {1}" -f $this.PSObject.TypeNames[0], $this.Name)} -Force -PassThru `
	| Add-Member -MemberType NoteProperty -Name Name -Value $name -PassThru `
	| Add-Member -MemberType NoteProperty -Name MaxHotVmsPerLun -Value $maxHotVmsPerLun -PassThru `
	| Add-Member -MemberType NoteProperty -Name MaxMediumVmsPerLun -Value $maxMediumVmsPerLun -PassThru `
	| Add-Member -MemberType NoteProperty -Name Luns -Value @() -PassThru `
	| Add-Member -MemberType ScriptMethod -Name AddLun -Value $addLunMethod -PassThru `
	| Add-Member -MemberType NoteProperty -Name LunIndex -Value @{} -PassThru `
	| Add-Member -MemberType ScriptMethod -Name GetLunByName -Value $getLunByNameMethod -PassThru `
	| Add-Member -MemberType NoteProperty -Name StorageCatz -Value $storageCatz -PassThru `
	| Add-Member -MemberType ScriptProperty -Name LunCount -Value $lunCountAccessor -PassThru `
	| Add-Member -MemberType ScriptMethod -Name GetLunsByCat -Value $getLunsByCatMethod -PassThru `
	| Add-Member -MemberType ScriptProperty -Name Vms -Value $vmsAccessor -PassThru `
	| Add-Member -MemberType ScriptMethod -Name GetVmsByCat -Value $getVmsByCatMethod -PassThru `
	| Add-Member -MemberType ScriptMethod -Name GetVmByName -Value $getVmByNameMethod -PassThru `
	| Add-Member -MemberType ScriptMethod -Name AddVm -Value $addVmMethod -PassThru `
	| Add-Member -MemberType ScriptProperty -Name VmCount -Value $vmCountAccessor -PassThru `
	| Add-Member -MemberType NoteProperty -Name VmIndex -Value @{} -PassThru `
	| Add-Member -MemberType ScriptMethod -Name PrintLayout -Value $printLayoutMethod -PassThru `
	| Add-Member -MemberType ScriptMethod -Name PrintLayoutByCat -Value $printLayoutByCatMethod -PassThru `
	| Add-Member -MemberType ScriptMethod -Name SetVmIoTypes -Value $setVmIoTypesMethod -PassThru `
	| Add-Member -MemberType ScriptMethod -Name SetVmIoTypesByCat -Value $setVmIoTypesByCatMethod -PassThru `
	| Add-Member -MemberType ScriptMethod -Name PrintLayoutByBurden -Value $printLayoutByBurdenMethod -PassThru `
	| Add-Member -MemberType ScriptMethod -Name PrintLayoutByFreeGB -Value $printLayoutByFreeGBMethod -PassThru `
	| Add-Member -MemberType ScriptMethod -Name PrintLayoutByName -Value $printLayoutByNameMethod -PassThru `

	$newLayout.PSObject.TypeNames.Clear()
	$newLayout.PSObject.TypeNames.Insert(0,'SDRS StorageLayout')
	return $newLayout
}

#endregion

#region Get-SdrsLun

function Get-SdrsLun
{
	param `
	(
		[string]$name, 
		[float]$capacityGB,
		$storageCatz
	)
	
	$usedGbAccessor = `
	{
		$i = 0
		foreach ( $vm in $this.Vms )
		{
			$i += $vm.CapacityGB
		}
		return $i
	}
	$freeGbAccessor = `
	{
		$this.CapacityGB - $this.UsedGB
	}
	$burdenAccessor = `
	{
		$i = 0
		foreach ( $vm in $this.Vms )
		{
			$i += $vm.IOAverage
		}
		return $i
	}
	$getStorageCatAccessor = `
	{
		Get-StorageCategory -name $this.Name -storageCatz $this.StorageCatz
	}
	$getVmCountAccessor = `
	{
		reurn $this.Vms.Count
	}
	$getHotIoVmsAccessor = `
	{
		$this.Vms | where { $_.IoType -eq "hot" }
	}
	$getMediumIoVmsAccessor = `
	{
		$this.Vms | where { $_.IoType -eq "medium" }
	}
	$getLowIoVmsAccessor = `
	{
		$this.Vms | where { $_.IoType -eq "low" }
	}
	$printLunMethod = `
	{
		Write-Host ( "`nDataStore: {0}" -f $this.Name )
		Write-Host ( "Capacity:  {0} GB" -f [Math]::Round($this.CapacityGB, 2) )
		Write-Host ( "Used:      {0} GB" -f [Math]::Round($this.UsedGB, 2 ) )
		Write-Host ( "Free:      {0} GB" -f [Math]::Round($this.FreeGB, 2 ) )
		Write-Host ( "Burden:    {0} KB/s" -f [Math]::Round($this.Burden, 2 ) )
		Write-Host "-------------------------------------------------------------------------------"
		foreach ( $type in @( "HotIOVms", "MediumIOVms", "LowIOVms" ) )
		{
			Write-Host ( "{0}:" -f $type )
			Write-Host "-------------------------------------------------------------------------------"
			foreach ( $vm in $this."$type" )
			{
				Write-Host ( "`tVm: {0, -25}, Capacity: {1, 6} GB, Burden: {2, 6} KB/s" -f $vm.Name, [Math]::Round($vm.CapacityGB, 2 ), [Math]::Round($vm.IOAverage, 2 ) )
			}
			Write-Host "-------------------------------------------------------------------------------"
		}
	}
	$addVmMethod = `
	{
		$vm = $args[0]
		$this.Vms += $vm
		$this.VmIndex[$vm.Name] = $this.Vms.Count - 1
	}
	$getVmByNameMethod = `
	{
		$vmName = $args[0]
		if ( -not ( $this.VmIndex.Keys -contains $vmName ) )
		{
			Throw ( "Unable to find VmIndex of Vm $vmName" )
		}
		$vmIndex = $this.VmIndex[$vmName]
		return [ref]$this.Vms[$vmIndex]
	}
	$removeVmMethod = `
	{
		$vm = $args[0]
		if ( -not ( $this.VmIndex.Keys -contains $vm.Name ) )
		{
			Throw "Unable to find VmIndex of Vm $vm.Name"
		}
		$index = $this.VmIndex[$vm.Name]
		$this.VmIndex.Remove($vm.Name)
		$tmp = @()
		$i = 0
		foreach ( $vm in $this.Vms )
		{
			if ( $i -eq $index ) { continue }
			$tmp += $this.Vms[$i]
			$i++
		}
		$this.Vms = $tmp
	}
	
	$newLun = New-Object System.Management.Automation.PSObject `
	| Add-Member -MemberType ScriptMethod -Name ToString -Value {("{0} object -> {1}" -f $this.PSObject.TypeNames[0], $this.Name)} -Force -PassThru `
	| Add-Member -MemberType NoteProperty -Name Name -Value $name -PassThru `
	| Add-Member -MemberType NoteProperty -Name StorageCatz -Value $storageCatz -PassThru `
	| Add-Member -MemberType ScriptProperty -Name StorageCat -Value $getStorageCatAccessor -PassThru `
	| Add-Member -MemberType NoteProperty -Name CapacityGB -Value $capacityGB -PassThru `
	| Add-Member -MemberType NoteProperty -Name Vms -Value @() -PassThru `
	| Add-Member -MemberType ScriptMethod -Name AddVm -Value $addVmMethod -PassThru `
	| Add-Member -MemberType ScriptMethod -Name GetVmByName -Value $getVmByNameMethod -PassThru `
	| Add-Member -MemberType ScriptMethod -Name RemoveVm -Value $removeVmMethod -PassThru `
	| Add-Member -MemberType NoteProperty -Name VmIndex -Value @{} -PassThru `
	| Add-Member -MemberType ScriptProperty -Name VmCount -Value $getVmCountAccessor -PassThru `
	| Add-Member -MemberType ScriptProperty -Name HotIoVms -Value $getHotIoVmsAccessor -PassThru `
	| Add-Member -MemberType ScriptProperty -Name MediumIoVms -Value $getMediumIoVmsAccessor -PassThru `
	| Add-Member -MemberType ScriptProperty -Name LowIoVms -Value $getLowIoVmsAccessor -PassThru `
	| Add-Member -MemberType ScriptProperty -Name UsedGB -Value $usedGbAccessor -PassThru `
	| Add-Member -MemberType ScriptProperty -Name FreeGB -Value $freeGbAccessor -PassThru `
	| Add-Member -MemberType ScriptProperty -Name Burden -Value $burdenAccessor -PassThru `
	| Add-Member -MemberType ScriptMethod -Name PrintLun -Value $printLunMethod -PassThru `
	
	$newLun.PSObject.TypeNames.Clear()
	$newLun.PSObject.TypeNames.Insert(0,'SDRS LUN')
	return $newLun
}

#endregion

#region Get-SdrsVm

function get-SdrsVm
{
	param `
	(
		[string]$name,
		[float]$capacityGb,
		[float]$ioAverage,
		[string]$currentLun,
		$storageCatz
	)
	
	$storageCatAccessor = `
	{
		Get-StorageCategory -name $this.CurrentLun -storageCatz $this.StorageCatz
	}
	
	$newVm = New-Object System.Management.Automation.PSObject `
	| Add-Member -MemberType ScriptMethod -Name ToString -Value {("{0} object -> {1}" -f $this.PSObject.TypeNames[0], $this.Name)} -Force -PassThru `
	| Add-Member -MemberType NoteProperty -Name Name -Value $name -PassThru `
	| Add-Member -MemberType NoteProperty -Name CapacityGB -Value $capacityGB -PassThru `
	| Add-Member -MemberType NoteProperty -Name IoAverage -Value $ioAverage -PassThru `
	| Add-Member -MemberType NoteProperty -Name IoType -Value $null -PassThru `
	| Add-Member -MemberType ScriptProperty -Name StorageCat -Value $storageCatAccessor -PassThru `
	| Add-Member -MemberType NoteProperty -Name CurrentLun -Value $currentLun -PassThru `
	| Add-Member -MemberType NoteProperty -Name StorageCatz -Value $storageCatz -PassThru `

	$newVm.PSObject.TypeNames.Clear()
	$newVm.PSObject.TypeNames.Insert(0,'SDRS VM')
	return $newVm
}

#endregion

#region Get-StorageDrs

function global:Get-StorageDrs
{
	param `
	(
		[string]$name,
		[string]$account,
		[string]$folder,
		[int]$maxVmsPerLun = 8,
		[int]$maxHotVmsPerLun = 1,
		[int]$maxMediumVmsPerLun = 2,
		[float]$minFreeGbPerLun = 5,
		$storageCatz
	)
	
	$getFolderAccessor = `
	{
		Get-Folder $this.FolderName -Server $this.Connection.Connection
	}
	$getCurrentLunsMethod = `
	{
		Write-Host ("Fetching data stores of folder {0}`n" -f $this.folderName)
		$cacheFile = (Get-CacheDir) + "datastores.xml"
		$storez = ""
		if ( -not ( Test-Path $cacheFile ) )
		{
			$this.Connection.Connect()
			$firstEnvHost = Get-VMHost -Location $this.Folder | Select -First 1 #-Server $this.Connection.Connection
			$storez = Get-Datastore -VMHost $firstEnvHost #-Server $this.Connection.Connection
			$storez | Export-Clixml $cacheFile
		}
		else
		{
			$storez = Import-Clixml $cacheFile
		}
		
		foreach ( $store in $storez )
		{
			$lun = Get-SdrsLun -name $store.Name -CapacityGB ($store.CapacityMB / 1KB) -storageCatz $this.StorageCatz
			$lunCopy = Get-SdrsLun -name $store.Name -CapacityGB ($store.CapacityMB / 1KB) -storageCatz $this.StorageCatz
			$this.CurrentLayout.AddLun($lun)
			$this.NewLayout.AddLun($lunCopy)
			Write-Host -NoNewline ("{0}, " -f $store.Name)
		}
		Write-Host "`n"
	}
	$getCurrentVmsMethod = `
	{
		Write-Host ("Fetching Vms of folder {0}`n" -f $this.folderName)
		$vmz = $null
		$cacheFile = (Get-CacheDir) + "Vms.xml"
		if ( -not ( Test-Path $cacheFile ) )
		{
			$this.Connection.Connect()
			$vmz = Get-VM -Location $this.Folder
			$vmz | Export-Clixml $cacheFile
		}
		else
		{
			$vmz = Import-Clixml $cacheFile
		}
		
		foreach ( $vm in $vmz )
		{
			$sdrsVm = ""
			$capacityGB = ( $vm.MemoryMB / 1KB )
			$lunType = ""
			$storageName = ""

			$cacheFile = (Get-CacheDir) + $vm.Name + ".hdd.xml"
			$vmhds = $null
			if ( -not (Test-Path $cacheFile) )
			{
				$this.Connection.Connect()
				$vmhds = get-harddisk -vm $vm
				$vmhds | Export-Clixml $cachefile
			}
			else
			{
				$vmhds = Import-Clixml $cachefile
			}
			
			foreach ( $vmhd in $vmhds )
			{
				if ( $vmhd.DiskType -ne "RawPhysical" )
				{
					$capacityGB += ($vmhd.CapacityKB / 1MB)
					$storageName = ($vmhd.Filename.split())[0]
					$storageName  = $storageName -replace("\[", "")
					$storageName  = $storageName -replace("\]", "")
				}
			}
				
			$ioAverage = 0

			$cacheFile = (Get-CacheDir) + $vm.Name + ".view.xml"
			$view = $null
			if ( -not (Test-Path $cacheFile) )
			{
				$this.Connection.Connect()
				$view = Get-View $vm.Id -Server $this.Connection.Connection
				$view | Export-Clixml $cacheFile
			}
			else
			{
				$view = Import-Clixml $cacheFile
			}
			
			$cacheFile = (Get-CacheDir) + $vm.Name + ".iostat.xml"
			$ioStat = $null
			if ( -not (Test-Path $cacheFile) )
			{
				$this.Connection.Connect()
				$ioStat = Get-VIStat -entity $view -start (Get-Date).AddDays(-7) -finish (Get-Date) -stat disk.usage.average -interval HI1 -connection $this.Connection
				$ioStat | Export-Clixml $cacheFile
			}
			else
			{
				$ioStat = Import-Clixml $cacheFile
			}
			if ( $ioStat )
			{
				$cacheFile = (Get-CacheDir) + $vm.Name + ".ioaverage.xml"
				$ioAverage = $null
				if ( -not (Test-Path $cacheFile) )
				{
					$ioAverage = ( $ioStat | Measure-Object -Property Value -Sum ).Sum / 3360
					$ioAverage | Export-Clixml $cacheFile
				}
				else
				{
					$ioAverage = Import-Clixml $cacheFile
				}
			}
			
			$sdrsVm = Get-SdrsVm -name $vm.Name -capacityGB $capacityGB -ioAverage $ioAverage -currentLun $storageName -storageCatz $this.StorageCatz
			$this.CurrentLayout.AddVm($sdrsVm)
			Write-Host -NoNewline ("{0}, " -f $vm.Name)
		}
		Write-Host "`n"
	}
	$getCurrentLayoutMethod = `
	{
		$this.CurrentLayout = ( Get-StorageLayout -name CurrentLayout -storageCatz $this.StorageCatz -maxHotVmsPerLun $this.maxHotVmsPerLun -maxMediumVmsPerLun $this.maxMediumVmsPerLun )
		$this.NewLayout = ( Get-StorageLayout -name NewLayout -storageCatz $this.StorageCatz -maxHotVmsPerLun $this.maxHotVmsPerLun -maxMediumVmsPerLun $this.maxMediumVmsPerLun )
		$this.GetCurrentLuns()
		$this.GetCurrentVms()
		$this.CurrentLayout.SetVmIoTypes()
	}
	$prepareTmpLayoutMethod = `
	{
		$this.TmpLayout = ( Get-StorageLayout -name TmpLayout -storageCatz $this.StorageCatz -maxHotVmsPerLun $this.maxHotVmsPerLun -maxMediumVmsPerLun $this.maxMediumVmsPerLun )
		foreach ( $cat in $this.StorageCatz.GetCatz )
		{
			$this.PrepareTmpLayoutPerCat($cat)
		}
	}
	$prepareTmpLayoutPerCatMethod = `
	{
		$cat = $args[0]
		if ( $cat.GetCatz )
		{
			foreach ( $subCat in $cat.GetCatz )
			{
				$this.PrepareTmpLayoutPerCat($subCat)
			}
		}
		else
		{
			$i = 0
			foreach ( $lun in $this.CurrentLayout.GetLunsByCat($cat) )
			{
				$name = ($cat.Name) + "_" + $i
				$this.TmpLayout.AddLun((Get-SdrsLun -name $name -storageCatz $this.StorageCatz))
				$i++
			}
		}
	}
	$getNewVmStorageTypesMethod = `
	{
		$this.PrepareTmpLayout()
		$dist = @{}
		
		foreach( $vm in $this.CurrentLayout.Vms )
		{
			$vmCat = $null
			foreach ( $cat in $this.StorageCatz.GetCatz )
			{
				$catLookup = $cat.Name + "*"
				if ( $vm.StorageCat -like $catLookup )
				{
					$vmCat = $cat
					break
				}
			}
			if ( $vmCat -ne $null )
			{
				if ( $vmCat.GetCatz )
				{
					$distString = $vm.Name -replace("\d+$", "")
					$newCat = $null
					if ( ($dist.Keys -contains $distString) )
					{
						$newCat = ($this.GetLeastUsedLunThatIsNotThisOne($vmCat, $dist[$distString])).Name
					}
					else
					{
						$newCat = ($this.GetLeastUsedLun($vmCat)).Name
					}
					$dist[$distString] = $newCat
					$newCat += "_0"
					$this.TmpLayout.AddVm($vm, $newCat)
				}
				else
				{
					$newCat = ($vmCat.Name) + "_0"
					$this.TmpLayout.AddVm($vm, $newCat)
				}
			}
			else
			{
				$vm
				throw "Vm Cat not found"
			}
		}
		$this.TmpLayout.SetVmIoTypes()
	}
	$GetLeastUsedLunMethod = `
	{
		$cat = $args[0]
		$leastUsed = $null
		$leastUsedCat = $null
		foreach ( $subCat in $cat.GetCatz )
		{
			$newCat = ($subCat.Name) + "_0"
			$used = $this.TmpLayout.GetLunByName($newCat).Value.UsedGB
			if ( ($leastUsed -eq $null) -or ($used -lt $leastUsed))
			{
				$leastUsed = $used
				$leastUsedCat = $subCat
			}
		}
		return $leastUsedCat
	}
	$GetLeastUsedLunThatIsNotThisOneMethod = `
	{
		$cat = $args[0]
		$notThisOne = $args[1]
		$leastUsed = $null
		$leastUsedCat = $null
		foreach ( $subCat in $cat.GetCatz )
		{
			if ( $subCat.Name -eq $notThisOne ) { continue }
			$newCat = ($subCat.Name) + "_0"
			$used = $this.TmpLayout.GetLunByName($newCat).Value.UsedGB
			if ( ($leastUsed -eq $null) -or ($used -lt $leastUsed))
			{
				$leastUsed = $used
				$leastUsedCat = $subCat
			}
		}
		return $leastUsedCat
	}
	$sortinVmsMethod = `
	{
		Write-Host ("Sorting in hot Vms")
		$this.SortInHotVms()
		Write-Host ("Sorting in medium Vms")
		$this.SortInMediumVms()
		Write-Host ("Sorting in low Vms")
		$this.SortInLowVms()
	}
	$sortinHotVmsMethod = `
	{
		if ( $args )
		{
			$cat = $args[0]
		}
		else
		{
			$cat = $this.StorageCatz
		}
		if ( $cat.GetCatz )
		{
			foreach ( $subCat in $cat.GetCatz | Sort Name )
			{
				$this.SortInHotVms($subCat)
			}
		}
		else
		{
			Write-Host ( "{0} Vms" -f $cat.Name)
			$luns = $this.NewLayout.GetLunsByCat($cat)
			$vms = $this.TmpLayout.GetVmsByCat($cat) | Where { $_.IOType -eq "hot" } | Sort IOAverage -Descending
			
			$i = 0
			foreach ( $vm in $vms )
			{
				Write-Host -NoNewline ("{0}, " -f $vm.Name)
				$this.NewLayout.GetLunByName($luns[$i].Name).Value.AddVm($vm)
				$i++
			}
			Write-Host "`n"
		}
	}
	$sortinMediumVmsMethod = `
	{
		if ( $args )
		{
			$cat = $args[0]
		}
		else
		{
			$cat = $this.StorageCatz
		}
		if ( $cat.GetCatz )
		{
			foreach ( $subCat in $cat.GetCatz | Sort Name )
			{
				$this.SortInMediumVms($subCat)
			}
		}
		else
		{
			Write-Host ( "{0} Vms" -f $cat.Name)
			$vms = $this.TmpLayout.GetVmsByCat($cat) | Where { $_.IOType -eq "medium" } | Sort IOAverage -Descending
			
			foreach ( $vm in $vms )
			{
				Write-Host -NoNewline ("{0}, " -f $vm.Name)
				$lunName = ($this.NewLayout.GetLunsByCat($cat) | where { (($_.FreeGB - $vm.CapacityGB) -ge $this.MinFreeGbPerLun) -and ($_.VMCount -lt $this.MaxVmsPerLun) } | Sort Burden | Select -First 1).Name
				if ( -not $lunName )
				{
					$lunName = $this.FreeLeastBurdenLunOfCat($cat, $vm.CapacityGB)
				}
				$this.NewLayout.GetLunByName($lunName).Value.AddVm($vm)
			}
			Write-Host "`n"
		}
	}
	$sortinLowVmsMethod = `
	{
		if ( $args )
		{
			$cat = $args[0]
		}
		else
		{
			$cat = $this.StorageCatz
		}
		if ( $cat.GetCatz )
		{
			foreach ( $subCat in $cat.GetCatz | Sort Name )
			{
				$this.SortInLowVms($subCat)
			}
		}
		else
		{
			Write-Host ( "{0} Vms" -f $cat.Name)
			$vms = $this.TmpLayout.GetVmsByCat($cat) | Where { $_.IOType -eq "low" } | Sort IOAverage -Descending
			
			foreach ( $vm in $vms )
			{
				Write-Host -NoNewline ("{0}, " -f $vm.Name)
				$lunName = ($this.NewLayout.GetLunsByCat($cat) | where { (($_.FreeGB - $vm.CapacityGB) -ge $this.MinFreeGbPerLun) -and ($_.VMCount -lt $this.MaxVmsPerLun) } | Sort Burden | Select -First 1).Name
				if ( -not $lunName )
				{
					Throw ("No solution found for VM $vm.Name")
				}
				$this.NewLayout.GetLunByName($lunName).Value.AddVm($vm)
			}
			Write-Host "`n"
		}
	}
	$freeLeastBurdenLunOfCatMethod = `
	{
		$cat = $args[0]
		$capacityNeeded = $args[1]
		
		$leastBurdenLunName = ($this.NewLayout.GetLunsByCat($cat) | Sort Burden | Select -First 1).Name
		$leastBurdenLun = $this.NewLayout.GetLunByName($leastBurdenLunName)

		Write-Host ( "`nNeed to free least burden LUN {0} with {1} GB free to get enough space" -f $leastBurdenLunName, $leastBurdenLun.Value.FreeGB )

		while ( ($leastBurdenLun.Value.FreeGB - $this.MinFreeGbPerLun ) -lt $capacityNeeded )
		{
			if ( $leastBurdenLun.Value.MediumIOVms )
			{
				$reassignVm = $leastBurdenLun.Value.MediumIOVms | Select -First 1
				Write-Host -NoNewline ("Reassigning Vm {0}" -f $reassignVm.Name )
				$leastBurdenLun.Value.RemoveVm($reassignVm)
				
				$nextLeastBurdenLunName = ($this.NewLayout.GetLunsByCat($cat) | Where { $_.Name -ne $leastBurdenLunName } | Sort Burden | Select -First 1).Name
				Write-Host (" to LUN {0}" -f $nextLeastBurdenLunName )
				$nextLeastBurdenLun = $this.NewLayout.GetLunByName($nextLeastBurdenLunName)
				$nextLeastBurdenLun.Value.AddVm($reassignVm)
			}
			elseif ( $leastBurdenLun.Value.HotIOVms )
			{
				$reassignVm = $leastBurdenLun.Value.HotIOVms | Select -First 1
				Write-Host -NoNewline ("Reassigning Vm {0}" -f $reassignVm.Name )
				$leastBurdenLun.Value.RemoveVm($reassignVm)
				
				$nextLeastBurdenLunName = ($this.NewLayout.GetLunsByCat($cat) | Where { $_.Name -ne $leastBurdenLunName } | Sort Burden | Select -First 1).Name
				Write-Host (" to LUN {0}" -f $nextLeastBurdenLunName )
				$nextLeastBurdenLun = $this.NewLayout.GetLunByName($nextLeastBurdenLunName)
				$nextLeastBurdenLun.Value.AddVm($reassignVm)
			}
			else
			{
				Throw "Unable to free LUN to get enough free space"
			}
		}
		return $leastBurdenLunName
	}
	
	$newSdrs = New-Object System.Management.Automation.PSObject `
	| Add-Member -MemberType ScriptMethod -Name ToString -Value {("{0} object -> {1}-{2}" -f $this.PSObject.TypeNames[0], $this.Name, $this.FolderName)} -Force -PassThru `
	| Add-Member -MemberType NoteProperty -Name Name -Value $name -PassThru `
	| Add-Member -MemberType NoteProperty -Name Account -Value $account -PassThru `
	| Add-Member -MemberType NoteProperty -Name MaxVmsPerLun -Value $maxVmsPerLun -PassThru `
	| Add-Member -MemberType NoteProperty -Name MaxHotVmsPerLun -Value $maxHotVmsPerLun -PassThru `
	| Add-Member -MemberType NoteProperty -Name MaxMediumVmsPerLun -Value $maxMediumVmsPerLun -PassThru `
	| Add-Member -MemberType NoteProperty -Name MinFreeGbPerLun -Value $minFreeGbPerLun -PassThru `
	| Add-Member -MemberType NoteProperty -Name StorageCatz -Value $storageCatz -PassThru `
	| Add-Member -MemberType ScriptProperty -Name Folder -Value $getFolderAccessor -PassThru `
	| Add-Member -MemberType NoteProperty -Name FolderName -Value $folder -PassThru `
	| Add-Member -MemberType NoteProperty -Name Connection -Value ( Get-ManagedHost -Name $name -account $account ) -PassThru `
	| Add-Member -MemberType NoteProperty -Name CurrentLayout -Value $null -PassThru `
	| Add-Member -MemberType ScriptMethod -Name GetCurrentLayout -Value $getCurrentLayoutMethod -PassThru `
	| Add-Member -MemberType ScriptMethod -Name GetCurrentLuns -Value $getCurrentLunsMethod -PassThru `
	| Add-Member -MemberType ScriptMethod -Name GetCurrentVms -Value $getCurrentVmsMethod -PassThru `
	| Add-Member -MemberType ScriptMethod -Name GetNewVmStorageTypes -Value $getNewVmStorageTypesMethod -PassThru `
	| Add-Member -MemberType ScriptMethod -Name PrepareTmpLayout -Value $prepareTmpLayoutMethod -PassThru `
	| Add-Member -MemberType ScriptMethod -Name PrepareTmpLayoutPerCat -Value $prepareTmpLayoutPerCatMethod -PassThru `
	| Add-Member -MemberType ScriptMethod -Name GetLeastUsedLun -Value $GetLeastUsedLunMethod -PassThru `
	| Add-Member -MemberType ScriptMethod -Name GetLeastUsedLunThatIsNotThisOne -Value $GetLeastUsedLunThatIsNotThisOneMethod -PassThru `
	| Add-Member -MemberType ScriptMethod -Name SortInVms -Value $sortinVmsMethod -PassThru `
	| Add-Member -MemberType ScriptMethod -Name SortInHotVms -Value $sortInHotVmsMethod -PassThru `
	| Add-Member -MemberType ScriptMethod -Name SortInMediumVms -Value $sortInMediumVmsMethod -PassThru `
	| Add-Member -MemberType ScriptMethod -Name SortInLowVms -Value $sortInLowVmsMethod -PassThru `
	| Add-Member -MemberType ScriptMethod -Name FreeLeastBurdenLunOfCat -Value $freeLeastBurdenLunOfCatMethod -PassThru `
	| Add-Member -MemberType NoteProperty -Name TmpLayout -Value $null -PassThru `
	| Add-Member -MemberType NoteProperty -Name NewLayout -Value $null -PassThru `
	
	$newSdrs.PSObject.TypeNames.Clear()
	$newSdrs.PSObject.TypeNames.Insert(0,'StorageDRS')
	return $newSdrs
}

#endregion

#region Testcode

Write-Host -ForegroundColor Red "--------------------Starting------------------------"
Remove-Variable t1 -ErrorAction SilentlyContinue


$sCatz = Get-StorageCat -name "AllCatz"
$sCatz.AddSubCat((Get-StorageCat -name "P1"))
$sCatz.AddSubCat((Get-StorageCat -name "P2"))
$sCatz.GetCatByName("P2").Value.AddSubCat((Get-StorageCat -name "P2A"))
$sCatz.GetCatByName("P2").Value.AddSubCat((Get-StorageCat -name "P2B"))
$sCatz.AddSubCat((Get-StorageCat -name "P3"))

$t1 = Get-StorageDrs -name msesxmgt02 -account ham-vmbatch -folder env-prod -storageCatz $sCatz
#$t1.CurrentLayout = Import-Clixml "E:\WindowsPowerShell\google\trunk\vStorageDRS\currentLayout.xml"
$t1.GetCurrentLayout()
$t1.CurrentLayout.GetVmByName("lxsrvcms01").Value.CapacityGB = 220
#$t1.CurrentLayout.PrintLayout()
$t1.GetNewVmStorageTypes()
#$t1.TmpLayout.PrintLayout()

#$t1.NewLayout.PrintLayout()

$t1.SortinVms()
#$t1.NewLayout.PrintLayout()



#$t1 = Get-ManagedHost -Name "msesxmgt02" -account "ham-vmbatch"
#Write-Host "1"
#$t1.Connect()
#Write-Host "2"
#$t1.Disconnect()
#Write-Host "3"
#

#endregion